/**   
* @Title: StreamUtilits.java 
* @Package net.gdface.utils 
* @Description: guyadong 
* @author guyadong   
* @date 2014-10-21 上午10:51:32 
* @version V1.0   
*/
package net.gdface.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 二进制数据(byte[],ByteBuffer)工具类
 * @author guyadong
 *
 */
public class BinaryUtils {
	private static final String MD5_REGEX = "^[a-fA-F0-9]{32}$";
	private static final String HEX_REGEX  = "^([a-fA-F0-9]{2})+$";
	/**
	 * 生成MD5校验码
	 * 
	 * @param source
	 * @return MD5 byte array
	 */
	public static final byte[] getMD5(byte[] source) {
		if (null ==source){
			return null;
		}
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			return md.digest(source);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 返回buffer中所有字节(position~limit),不改变buffer状态
	 * @param buffer
	 * @return buffer 为 null 时返回 null 
	 */
	public static final byte[] getBytesInBuffer(ByteBuffer buffer){
		if(null == buffer){
			return null;
		}
		int pos = buffer.position();
		try{
			byte[] bytes = new byte[buffer.remaining()];
			buffer.get(bytes);
			return bytes;
		}finally{
			buffer.position(pos);
		}
	}
	/**
	 * 生成MD5校验码
	 * @param source
	 * @return MD5 ByteBuffer
	 * @see #getMD5(byte[])
	 */
	public static final ByteBuffer getMD5(ByteBuffer source) {
		return null == source ?null:ByteBuffer.wrap(getMD5(getBytesInBuffer(source)));
	}
	/**
	 * 将16位byte[] 转换为32位的HEX格式的字符串String
	 * 
	 * @param buffer
	 * @return HEX string
	 */
	public static final String toHex(byte[] buffer) {
		if (null == buffer){
			return null;
		}
		StringBuffer sb = new StringBuffer(buffer.length * 2);
		for (int i = 0; i < buffer.length; i++) {
			sb.append(Character.forDigit((buffer[i] & 240) >> 4, 16));
			sb.append(Character.forDigit(buffer[i] & 15, 16));
		}
		return sb.toString();
	}
	/** @see #toHex(byte[]) */
	public static final String toHex(ByteBuffer buffer) {
		return toHex(getBytesInBuffer(buffer));  
	}
	/**
	 * 字符串验证器,根据正则表达式判断字符串是否为十六进制(HEX)字符串
	 * 输入为null或空或正则表达式不匹配则返回false
	 */
	public static final boolean validHEX(String input){
		return input != null && input.matches(HEX_REGEX);
	}
    /**
     * convert HEX string to byte array
     * @param src
     * @return byte array or {@code null} if src is {@code null}
     */
    public static final byte[] hex2Bytes(String src){
    	if(null == src){
    		return null;
    	}
        byte[] res = new byte[src.length()/2];  
        char[] chs = src.toCharArray();  
        int[] b = new int[2];  
  
        for(int i=0,c=0; i<chs.length; i+=2,c++){              
            for(int j=0; j<2; j++){  
                if(chs[i+j]>='0' && chs[i+j]<='9'){  
                    b[j] = (chs[i+j]-'0');  
                }else if(chs[i+j]>='A' && chs[i+j]<='F'){  
                    b[j] = (chs[i+j]-'A'+10);  
                }else if(chs[i+j]>='a' && chs[i+j]<='f'){  
                    b[j] = (chs[i+j]-'a'+10);  
                }  
            }   
              
            b[0] = (b[0]&0x0f)<<4;  
            b[1] = (b[1]&0x0f);  
            res[c] = (byte) (b[0] | b[1]);  
        }  
          
        return res;  
    } 
    /**
     * convert HEX string to ByteBuffer
     * @param src
     * @return byte array or {@code null} if src is {@code null}
     */
    public static final ByteBuffer hex2ByteBuffer(String src){
    	return null == src?null:ByteBuffer.wrap(hex2Bytes(src));
    }
	/**
	 * 生成MD5校验码字符串
	 * 
	 * @param source
	 * @return MD5 HEX string
	 * @see #getMD5(byte[])
	 * @see #toHex(byte[])
	 */
    public static final String getMD5String(byte[] source) {
		return toHex(getMD5(source));
	}
	/**
	 * 生成MD5校验码字符串
	 * 
	 * @param source
	 * @return MD5 HEX string
	 * @see #getMD5(byte[])
	 * @see #toHex(byte[])
	 */
    public static final String getMD5String(ByteBuffer source) {
		return toHex(getMD5(source));
	}
	/**
	 * 判断是否为有效的MD5字符串
	 * @return true/false
	 */
	public static final boolean validMd5(String md5){
		return null != md5 && md5.matches(MD5_REGEX);
	}
	/**
	 * 从{@link InputStream}读取字节数组<br>
	 * 当{@code in}为{@link FileInputStream}时，调用{@link #readBytes(FileInputStream)}(NIO方式)读取<br>
	 *  结束时会关闭{@link InputStream}
	 * @param in 为{@code null}返回{@code null}
	 * @return 读取的字节数组
	 * @throws IOException
	 * @throws IllegalArgumentException {@code in}为{@code null}
	 */
	public static final byte[] readBytes(InputStream in) throws IOException, IllegalArgumentException {
		if(null == in){
			return null;
		}
		if(in instanceof FileInputStream){
			return readBytes((FileInputStream)in);
		}
		try {
			int buffSize = Math.max(in.available(), 1024*8);
			byte[] temp = new byte[buffSize];
			ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);
			int size = 0;
			while ((size = in.read(temp)) != -1) {
				out.write(temp, 0, size);
			}
			return  out.toByteArray();
		} finally {
			in.close();
		}
	}
	
	/**
	 * NIO方式从{@link FileInputStream}读取字节数组<br>
	 *  结束时会关闭{@link InputStream}
	 * @param fin {@link FileInputStream}
	 * @return 返回读取的字节数 当{@code fin}为null时返回null;
	 * @throws IOException
	 */
	public static final byte[] readBytes(FileInputStream fin) throws IOException {
		if(null == fin){
			return null;
		}
		FileChannel fc = fin.getChannel();
		try {
			ByteBuffer bb = ByteBuffer.allocate((int) fc.size());
			fc.read(bb);
			bb.flip();
			return bb.array();
		} finally {
			if (null != fc){
				fc.close();
			}
			fin.close();
		}
	}
	/**
	 * 将对象转换为InputStream<br>
	 * 类型可以是byte[],{@link ByteBuffer},{@link InputStream},{@link String}(base64编码),{@link File},{@link URL},{@link URI},否则抛出RuntimeException<br>
	 * 
	 * @param src
	 *            获取InputStream的源对象
	 * @return 返回获取的InputStream对象,src为{@code null}返回{@code null},类型错误抛出异常
	 * @throws IOException
	 * @throws IllegalArgumentException 无法从{@code src}获取{@link InputStream}
	 */
	public static final <T> InputStream getInputStream(T src) throws IOException, IllegalArgumentException {
		if(null == src){
			return null;
		}
		if (src instanceof InputStream){
			return (InputStream) src;
		}else if (src instanceof String) {
			return new ByteArrayInputStream(Base64Utils.decode(((String) src)));
		} else if (src instanceof byte[]) {
			return new ByteArrayInputStream((byte[]) src);
		} else if (src instanceof ByteBuffer) {
			return new ByteArrayInputStream(getBytesInBuffer((ByteBuffer) src));
		} else if (src instanceof File) {
			return new FileInputStream((File) src);
		} else if (src instanceof URL) {
			return ((URL) src).openStream();
		} else if (src instanceof URI) {
			return ((URI) src).toURL().openStream();
		} else{
			throw new IllegalArgumentException(String.format("Can't get inputstream from [%s]", src.getClass()
					.getCanonicalName()));
		}
	}

	/**
	 * 将数据对象{@code src}转换为字节数组(byte[])<br>
	 * {@code src}的数据类型可以是byte[],{@link InputStream},{@link ByteBuffer},{@link String}(base64编码),{@link File},{@link URL},{@link URI}
	 * 否则抛出{@link IllegalArgumentException}<br>
	 * 对象转换为InputStream或byte[]时,可能会抛出{@link IOException}
	 * 
	 * 当{@code src}为{@link File}或{@link FileInputStream}时，使用NIO方式({@link #readBytes(FileInputStream)})读取
	 * 
	 * @param src
	 *            获取byte[]的源对象
	 * @return 返回字节数组,参数为{@code null}返回{@code null},类型不对则抛出异常
	 * @throws IOException
	 * @throws IllegalArgumentException {@code src}为{@code null}或无法从{@code src}获取{@link InputStream}
	 * @see #readBytes(InputStream)
	 * @see #readBytes(FileInputStream)
	 * @see #getInputStream(Object)
	 * @see Base64Utils#decode(String)
	 */
	public static final <T> byte[] getBytes(T src) throws IOException, IllegalArgumentException {
		if(null==src){
			return null;
		}
		if (src instanceof byte[]) {
			return (byte[]) src;
		} else if (src instanceof String) {
			return Base64Utils.decode(((String) src));
		} else if (src instanceof ByteBuffer) {
			return getBytesInBuffer((ByteBuffer)src);
		} else if (src instanceof FileInputStream){
			return readBytes((FileInputStream)src);
		}else if (src instanceof File){
			return readBytes(new FileInputStream((File)src));
		}else {
			return readBytes(getInputStream(src));
		}
	}
	
	/**
	 * 调用 {@link #getBytes(Object)}返回非空字节数组<br>
	 * 如果返回{@code null}或空字节数组，则抛出{@link IOException}<br>
	 * @param src 获取byte[]的源对象
	 * @return 返回非空字节数组
	 * @throws IOException
	 * @see #getBytes(Object)
	 */
	public static final <T> byte[] getBytesNotEmpty(T src) throws IOException {
		byte[] imgData = getBytes(src);
		if (imgData == null || imgData.length == 0){
			throw new IOException(String.format("return null or zero length from %s", src.getClass()
					.getSimpleName()));
		}
		return imgData;
	}

	/**
	 * 将数据对象src转换为{@link ByteBuffer}
	 * @param src 获取byte[]的源对象
	 * @return 返回字节数组,参数为{@code null}返回{@code null},类型不对则抛出异常
	 * @throws IOException
	 * @throws IllegalArgumentException 无法从{@code src}获取{@link InputStream}
	 * @see #getBytes(Object)
	 */
	public static final <T> ByteBuffer getByteBuffer(T src) throws IOException, IllegalArgumentException {
		return src == null ? null : ByteBuffer.wrap(getBytes(src));
	}
	
	/**
	 * 调用 {@link #getByteBuffer(Object)}返回非空{@link ByteBuffer}<br>
	 * 如果返回{@code null}或空，则抛出{@link IOException}<br>
	 * @param src
	 * @return 返回非空字节数组
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public static final <T> ByteBuffer getByteBufferNotEmpty(T src) throws IOException, IllegalArgumentException {
		return ByteBuffer.wrap(getBytesNotEmpty(src));
	}
	/**
	 * NIO方式将{@code data}数据保存在{@code file}指定的文件中<br>
	 * 如果{@code file}所在文件夹不存在，则会自动创建所有的文件夹<br>
	 * @param data
	 * @param file 文件保存的位置
	 * @param overwrite 同名文件存在时是否覆盖
	 * @return 返回保存的文件名
	 * @throws IOException {@code file}存在但不是文件或其他IO异常
	 * @throws IllegalArgumentException {@code data}为null时
	 */
	public static final File saveBytes(byte[] data, File file, boolean overwrite) throws IOException,
			IllegalArgumentException {
		if(data == null){
			throw new IllegalArgumentException("data is null");
		}
		FileOutputStream out = null;
		FileChannel fc = null;
		try {
			File folder = file.getParentFile();
			if (!folder.exists()){
				folder.mkdirs();
			}
			long free = folder.getFreeSpace()>>20;//可用磁盘空间(MB)
			if(free<10){
				throw new IOException(String.format("DISK ALMOST FULL(磁盘空间不足) FREE %dMB,%s",free,folder.getAbsolutePath()));
			}
			if (!file.exists() || !file.isFile() || overwrite) {
				out = new FileOutputStream(file);
				fc = out.getChannel();
				ByteBuffer bb = ByteBuffer.wrap(data);
				fc.write(bb);
			}
			return file;
		} finally {
			if (null != fc){
				fc.close();
			}
			if (null != out){
				out.close();
			}
		}
	}
}
